//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/image/algorithm.h>
#include <buola/image/cimageplugin.h>
#include <buola/image/canyimage.h>
#include <buola/io/cbufferstream.h>

extern "C"
{
    #include <jpeglib.h>
}

namespace buola { namespace img {

class CJPEGCodec : public CCodec
{
public:
    CJPEGCodec();
    virtual ~CJPEGCodec();

    void Read(io::PBufferStream pSrc,CAnyImage &pDst,EImageFormat pFmt,const CSize_i &pSize);
    void Write(const CAnyView &pSrc,io::PBufferStream pDst);

protected:
    jpeg_decompress_struct mJPGFile;
    jpeg_error_mgr mJPGError;
};

class CJPEGPlugin : public CImagePlugin
{
public:
    CJPEGPlugin()
    {
        RegisterPlugin("jpg",this);
        RegisterPlugin("jpeg",this);
        RegisterPlugin("JPG",this);
        RegisterPlugin("JPEG",this);
    }
    
    CCodec *GetInstance()
    {
        return new CJPEGCodec;
    }
};

static CJPEGPlugin sJPEGPlugin;

struct CJPEGSourceManager
{
public:
    struct jpeg_source_mgr mPub;
    io::CBufferStream *mStream;
    char *mBuffer;
};

const int JPEG_INPUT_BUF_SIZE=4096;

static void buola_jpeg_init_source(j_decompress_ptr cinfo)
{
    CJPEGSourceManager *lMgr=reinterpret_cast<CJPEGSourceManager*>(cinfo->src);
    
    lMgr->mBuffer=alloc_array<char>(JPEG_INPUT_BUF_SIZE);
}

static boolean buola_jpeg_fill_input_buffer(j_decompress_ptr cinfo)
{
    CJPEGSourceManager *lMgr=(CJPEGSourceManager*)cinfo->src;
    
    io::CResult lResult=lMgr->mStream->Read((uint8_t*)lMgr->mBuffer,JPEG_INPUT_BUF_SIZE);
    
    if(!lResult.Count())
        throw XInternal(I18N("error reading jpeg file"));
    
    lMgr->mPub.next_input_byte=(JOCTET*)lMgr->mBuffer;
    lMgr->mPub.bytes_in_buffer=lResult.Count();
    
    return true;
}

static void buola_jpeg_skip_input_data(j_decompress_ptr cinfo,long pCount)
{
    CJPEGSourceManager *lMgr=(CJPEGSourceManager*)cinfo->src;

    if(pCount<=lMgr->mPub.bytes_in_buffer)
    {
        lMgr->mPub.bytes_in_buffer-=pCount;
        lMgr->mPub.next_input_byte+=pCount;
        return;
    }
    else
    {
        pCount-=lMgr->mPub.bytes_in_buffer;
        lMgr->mPub.bytes_in_buffer=0;
        lMgr->mStream->Ignore(pCount);
    }
}

static void buola_jpeg_terminate_source(j_decompress_ptr /*cinfo*/)
{
}

static void jpeg_buola_src(j_decompress_ptr cinfo,io::PBufferStream pStream)
{
    CJPEGSourceManager *lMgr=new CJPEGSourceManager;

    cinfo->src=(jpeg_source_mgr*)lMgr;
    
    lMgr->mPub.init_source=buola_jpeg_init_source;
    lMgr->mPub.fill_input_buffer=buola_jpeg_fill_input_buffer;
    lMgr->mPub.skip_input_data=buola_jpeg_skip_input_data;
    lMgr->mPub.resync_to_restart=jpeg_resync_to_restart;
    lMgr->mPub.term_source=buola_jpeg_terminate_source;
    
    lMgr->mStream=pStream.get();
    lMgr->mBuffer=nullptr;

    lMgr->mPub.bytes_in_buffer=0;
    lMgr->mPub.next_input_byte=nullptr;
}

CJPEGCodec::CJPEGCodec()
{
}

CJPEGCodec::~CJPEGCodec()
{
    jpeg_destroy_decompress(&mJPGFile);
}

void CJPEGCodec::Read(io::PBufferStream pSrc,CAnyImage &pDst,EImageFormat pFmt,const CSize_i& /*pSize*/)
{
    mJPGFile.err=jpeg_std_error(&mJPGError);
    jpeg_create_decompress(&mJPGFile);
    jpeg_buola_src(&mJPGFile,pSrc);

    jpeg_read_header(&mJPGFile,true);

    mJPGFile.out_color_space=JCS_RGB;
    jpeg_start_decompress(&mJPGFile);

    if(mJPGFile.num_components<3)
        throw XInternal(I18N("can't load JPEG file"));

    int lW=mJPGFile.output_width;
    int lH=mJPGFile.output_height;
    int lC=mJPGFile.num_components;
    int lRowWidth=lW*lC;

    CImage_rgb8 lDst({lW,lH});
    
    if(lC==3) //we can use the image we have directly
    {
        unsigned char *lBuffer=lDst.RawData();
        
        JSAMPROW lSample[1];
        
        for(int i=0;i<lH;i++)
        {
            lSample[0]=(JSAMPROW)(lBuffer+(i*lRowWidth));
            jpeg_read_scanlines(&mJPGFile,lSample,1);
        }
    }
    else
    {
        throw XInternal(I18N("strange number of components in jpeg file"));
    }

    jpeg_finish_decompress(&mJPGFile);

    pDst=std::move(lDst);
}

void CJPEGCodec::Write(const img::CAnyView &pSrc,io::PBufferStream pDst)
{
    msg_warn() << "not implemented CJPEGCodec::Write\n";
}

/*namespace img*/ } /*namespace buola*/ }
